--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit ec8d43a548017de41cdf73e7e3eb9844a5d349cd
Parents : 5081255
Author : Mark Qvist <bc7291552be7a58f361522990465165c>
Signature : T66BB85Valid, signed by author
Date : 2026-07-19T00:57:27+02:00
Updated rnsh config args to work similarly to other RNS utils
Changes
5 files changed, 87 insertions(+), 65 deletions(-)
Diff
diff --git a/RNS/Utilities/rnsh/args.py b/RNS/Utilities/rnsh/args.py
index 71a411d84..752446018 100644
--- a/RNS/Utilities/rnsh/args.py
+++ b/RNS/Utilities/rnsh/args.py
@@ -44,7 +44,8 @@ def setup_argument_parser():
parser = argparse.ArgumentParser(description="Reticulum Remote Shell Utility", epilog="When specifying a command to execute, separate rnsh\noptions from the command and its arguments with --\n\nFor example:\n rnsh -l -- /bin/bash --login\n rnsh <destination> -- ls -la /tmp", formatter_class=argparse.RawDescriptionHelpFormatter)
# Common options
- parser.add_argument("--config", "-c", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
+ parser.add_argument("--config", "-c", action="store", default=None, help="path to config directory", type=str)
+ parser.add_argument("--rnsconfig", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument("--identity", "-i", action="store", default=None, help="path to identity file to use", type=str)
parser.add_argument("-v", "--verbose", action="count", default=0, help="increase verbosity")
parser.add_argument("-q", "--quiet", action="count", default=0, help="decrease verbosity")
diff --git a/RNS/Utilities/rnsh/initiator.py b/RNS/Utilities/rnsh/initiator.py
index 67efa68b1..fa008a7ce 100644
--- a/RNS/Utilities/rnsh/initiator.py
+++ b/RNS/Utilities/rnsh/initiator.py
@@ -144,7 +144,7 @@ class RemoteExecutionError(Exception):
def __init__(self, msg): self.msg = msg
-async def _initiate_link(configdir, rnsconfigdir, identitypath=None, verbosity=0, quietness=0, noid=False, destination=None,
+async def _initiate_link(configdir, rnsconfigdir, identitypath=None, logfile=None, verbosity=0, quietness=0, noid=False, destination=None,
timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
global _identity, _reticulum, _link, _destination, _remote_exec_grace
@@ -160,11 +160,11 @@ async def _initiate_link(configdir, rnsconfigdir, identitypath=None, verbosity=0
if _reticulum is None:
targetloglevel = compute_target_rns_loglevel(verbosity, quietness, RNS.LOG_ERROR)
- RNS.logfile = os.path.join(configdir, "logfile")
+ RNS.logfile = logfile
_reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=targetloglevel, logdest=RNS.LOG_FILE)
if _identity is None:
- _identity = rnsh.prepare_identity(identitypath)
+ _identity = rnsh.prepare_identity(identity_path=identitypath, service_name=None, configdir=configdir)
if not RNS.Transport.has_path(destination_hash):
RNS.Transport.request_path(destination_hash)
@@ -211,8 +211,9 @@ async def _handle_error(errmsg: RNS.MessageBase):
raise RemoteExecutionError(f"Remote error: {errmsg.msg}")
-async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, verbosity: int, quietness: int, noid: bool, destination: str,
+async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:str, verbosity: int, quietness: int, noid: bool, destination: str,
timeout: float, command: [str] | None = None):
+
global _finished, _link
if timeout is None:
timeout = RNS.Transport.PATH_REQUEST_TIMEOUT
diff --git a/RNS/Utilities/rnsh/listener.py b/RNS/Utilities/rnsh/listener.py
index f9a1ec938..97f585fad 100644
--- a/RNS/Utilities/rnsh/listener.py
+++ b/RNS/Utilities/rnsh/listener.py
@@ -118,25 +118,32 @@ def compute_target_rns_loglevel(verbosity: int, quietness: int, base_level: int
except Exception: return base_level
-async def listen(configdir, rnsconfigdir, command, identitypath=None, service_name=None, verbosity=0, quietness=0, allowed=None,
- allowed_file=None, disable_auth=None, announce_period=900, no_remote_command=True, remote_cmd_as_args=False,
+async def listen(configdir, rnsconfigdir, command, identitypath=None, logfile=None, service_name=None, verbosity=0, quietness=0,
+ allowed=None, allowed_file=None, disable_auth=None, announce_period=900, no_remote_command=True, remote_cmd_as_args=False,
loop: asyncio.AbstractEventLoop = None):
global _identity, _allow_all, _allowed_identity_hashes, _allowed_file, _allowed_file_identity_hashes
global _reticulum, _cmd, _destination, _no_remote_command, _remote_cmd_as_args, _finished
if not loop: loop = asyncio.get_running_loop()
- if service_name is None or len(service_name) == 0:
- service_name = "default"
+ if service_name is None or len(service_name) == 0: service_name = "default"
RNS.log(f"Using service name {service_name}", RNS.LOG_INFO)
+ if logfile:
+ RNS.log(f"Logging to {logfile}", RNS.LOG_NOTICE)
+ logdest = RNS.LOG_FILE
+ RNS.logfile = logfile
+ else:
+ RNS.log(f"Logging to console", RNS.LOG_NOTICE)
+ logdest = RNS.LOG_STDOUT
# More -v should increase verbosity (higher RNS.loglevel); -q should decrease it
targetloglevel = compute_target_rns_loglevel(verbosity, quietness, RNS.LOG_INFO)
_reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=targetloglevel)
- _identity = rnsh.prepare_identity(identitypath, service_name)
+ _identity = rnsh.prepare_identity(identity_path=identitypath, service_name=service_name, configdir=configdir)
_destination = RNS.Destination(_identity, RNS.Destination.IN, RNS.Destination.SINGLE, rnsh.APP_NAME)
RNS.log(f"rnsh listening for commands on {RNS.prettyhexrep(_destination.hash)}", RNS.LOG_NOTICE)
+ RNS.logdest = logdest
_cmd = command
if _cmd is None or len(_cmd) == 0:
diff --git a/RNS/Utilities/rnsh/rnsh.py b/RNS/Utilities/rnsh/rnsh.py
index 2d804217d..99ad24e1d 100644
--- a/RNS/Utilities/rnsh/rnsh.py
+++ b/RNS/Utilities/rnsh/rnsh.py
@@ -57,43 +57,54 @@ loop: asyncio.AbstractEventLoop | None = None
def _sanitize_service_name(service_name:str) -> str: return re.sub(r'\W+', '', service_name)
-def prepare_identity(identity_path, service_name: str = None) -> tuple[RNS.Identity]:
+def prepare_identity(identity_path=None, service_name=None, configdir=None):
service_name = _sanitize_service_name(service_name or "")
- if identity_path is None:
- identity_path = RNS.Reticulum.identitypath + "/" + APP_NAME + \
- (f".{service_name}" if service_name and len(service_name) > 0 else "")
+ if not identity_path:
+ identity_path = f"{configdir}/identity" + (f".{service_name}" if service_name and len(service_name) > 0 else "")
identity = None
if os.path.isfile(identity_path):
+ RNS.log(f"Loading identity from {identity_path}", RNS.LOG_VERBOSE)
identity = RNS.Identity.from_file(identity_path)
if identity is None:
- RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
+ RNS.log(f"Creating identity {identity_path}", RNS.LOG_NOTICE)
identity = RNS.Identity()
identity.to_file(identity_path)
return identity
-def print_identity(configdir, identitypath, service_name, include_destination: bool):
- reticulum = RNS.Reticulum(configdir=configdir, loglevel=RNS.LOG_INFO)
- if service_name and len(service_name) > 0:
- print(f"Using service name \"{service_name}\"")
- identity = prepare_identity(identitypath, service_name)
+def print_identity(configdir, rnsconfigdir, identitypath, service_name, include_destination: bool):
+ reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=RNS.LOG_INFO)
+ if service_name and len(service_name) > 0: print(f"Using service name \"{service_name}\"")
+ identity = prepare_identity(identity_path=identitypath, service_name=service_name, configdir=configdir)
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME)
print("Identity : " + str(identity))
- if include_destination:
- print("Listening on : " + RNS.prettyhexrep(destination.hash))
+ if include_destination: print("Listening on : " + RNS.prettyhexrep(destination.hash))
exit(0)
verbose_set = False
-def ensure_config_directory():
- if os.path.isdir(os.path.expanduser("~/.config/rnsh")): return os.path.expanduser("~/.config/rnsh")
- elif os.path.isdir(os.path.expanduser("~/.rnsh")): return os.path.expanduser("~/.rnsh")
+def ensure_config_directory(configdir=None):
+ if configdir:
+ if os.path.isdir(os.path.expanduser(configdir)): return os.path.expanduser(configdir)
+ else:
+ try:
+ RNS.log(f"Creating configuration directory: {os.path.expanduser(configdir)}")
+ os.makedirs(os.path.expanduser(configdir))
+ return os.path.expanduser(configdir)
+
+ except Exception as e:
+ RNS.log(f"Could not get or create rnsh configuration directory, aborting", RNS.LOG_CRITICAL)
+ os._exit(1)
+
+ elif os.path.isdir(os.path.expanduser("~/.config/rnsh")): return os.path.expanduser("~/.config/rnsh")
+ elif os.path.isdir(os.path.expanduser("~/.rnsh")): return os.path.expanduser("~/.rnsh")
else:
try:
+ RNS.log(f"Creating configuration directory: {os.path.expanduser('~/.rnsh')}")
os.makedirs(os.path.expanduser("~/.rnsh"))
return os.path.expanduser("~/.rnsh")
@@ -106,24 +117,29 @@ async def _rnsh_cli_main():
global verbose_set
args, parser = parse_arguments()
verbose_set = args.verbose > 0
- configdir = ensure_config_directory()
+ configdir = ensure_config_directory(args.config)
+
+ if not configdir:
+ RNS.log(f"Could not resolve rnsh configuration directory", RNS.LOG_CRITICAL)
+ os._exit(1)
if args.print_identity:
- print_identity(args.config, args.identity, args.service, args.listen)
+ print_identity(configdir, args.rnsconfig, args.identity, args.service, args.listen)
return 0
+ logfile = f"{configdir}/logfile"
+
if args.listen:
allowed_file = None
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
- if os.path.isfile(os.path.expanduser("~/.config/rnsh/allowed_identities")):
- allowed_file = os.path.expanduser("~/.config/rnsh/allowed_identities")
- elif os.path.isfile(os.path.expanduser("~/.rnsh/allowed_identities")):
- allowed_file = os.path.expanduser("~/.rnsh/allowed_identities")
+ if os.path.isfile(os.path.expanduser(f"{configdir}/allowed_identities")):
+ allowed_file = os.path.expanduser(f"{configdir}/allowed_identities")
await listener.listen(configdir=configdir,
- rnsconfigdir=args.config,
+ rnsconfigdir=args.rnsconfig,
command=args.command,
identitypath=args.identity,
+ logfile=logfile,
service_name=args.service,
verbosity=args.verbose,
quietness=args.quiet,
@@ -137,8 +153,9 @@ async def _rnsh_cli_main():
if args.destination is not None:
return_code = await initiator.initiate(configdir=configdir,
- rnsconfigdir=args.config,
+ rnsconfigdir=args.rnsconfig,
identitypath=args.identity,
+ logfile=logfile,
verbosity=args.verbose,
quietness=args.quiet,
noid=args.no_id,
diff --git a/docs/source/using.rst b/docs/source/using.rst
index 2ca8d6076..683751bb6 100644
--- a/docs/source/using.rst
+++ b/docs/source/using.rst
@@ -1002,39 +1002,35 @@ available. These are only recognised immediately after a newline character:
destination hexadecimal hash of the destination to connect to
options:
- -h, --help show this help message and exit
- --config, -c CONFIG path to alternative Reticulum config directory
- --identity, -i IDENTITY
- path to identity file to use
- -v, --verbose increase verbosity
- -q, --quiet decrease verbosity
- -p, --print-identity print identity and destination info and exit
- --version show program's version number and exit
- -l, --listen listen (server) mode; any command specified after --
- will be used as the default command when the initiator
- does not provide one or when remote command execution
- is disabled; if no command is specified, the default
- shell of the user running rnsh will be used
- -s, --service SERVICE
- service name for identity file if not the default
- -b, --announce PERIOD
- announce on startup and every PERIOD seconds; specify
- 0 to announce on startup only
- -a, --allowed HASH allow this identity to connect (may be specified
- multiple times); allowed identities can also be
- specified in ~/.rnsh/allowed_identities or
- ~/.config/rnsh/allowed_identities, one hash per line
- -n, --no-auth disable authentication (allow any identity to connect)
+ -h, --help show this help message and exit
+ --config, -c PATH path to config directory
+ --rnsconfig, -c PATH path to alternative Reticulum config directory
+ --identity, -i IDENTITY path to identity file to use
+ -v, --verbose increase verbosity
+ -q, --quiet decrease verbosity
+ -p, --print-identity print identity and destination info and exit
+ --version show program's version number and exit
+ -l, --listen listen (server) mode; any command specified after --
+ will be used as the default command when the initiator
+ does not provide one or when remote command execution
+ is disabled; if no command is specified, the default
+ shell of the user running rnsh will be used
+ -s, --service SERVICE service name for identity file if not the default
+ -b, --announce PERIOD announce on startup and every PERIOD seconds; specify
+ 0 to announce on startup only
+ -a, --allowed HASH allow this identity to connect (may be specified
+ multiple times); allowed identities can also be
+ specified in ~/.rnsh/allowed_identities or
+ ~/.config/rnsh/allowed_identities, one hash per line
+ -n, --no-auth disable authentication (allow any identity to connect)
-A, --remote-command-as-args
- concatenate remote command to the argument list of the
- default program or shell
- -C, --no-remote-command
- disable executing command lines received from the
- remote initiator
- -N, --no-id disable identity announcement on connect
- -m, --mirror return with the exit code of the remote process
- -w, --timeout SECONDS
- connect and request timeout in seconds
+ concatenate remote command to the argument list of the
+ default program or shell
+ -C, --no-remote-command disable executing command lines received from the
+ remote initiator
+ -N, --no-id disable identity announcement on connect
+ -m, --mirror return with the exit code of the remote process
+ -w, --timeout SECONDS connect and request timeout in seconds
When specifying a command to execute, separate rnsh options from the command
and its arguments with --. For example:
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────